// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace SnapObjects.Data
{
    internal class SqlCommandBuilder : CommandBuilder
    {
        public SqlCommandBuilder(DataContext dataContext)
            : base(dataContext, new CommandBuilderContext())
        {
        }

        public SqlCommandBuilder(
            DataContext dataContext,
            CommandBuilderContext builderContext)
            : base(dataContext, builderContext)
        {
        }

        public void BuildCommand(IUpdatableSql updatableSql)
        {
            this.Init();

            var tokenizer = this.BuildParameters(updatableSql);

            var resultSetMapping = this.BuildSql(tokenizer.SqlStatement);

            this.AddAffectedCountOutputParameter(this.SqlBuilder, null);

            this.AddResultSetMapping(resultSetMapping);
        }

        //Batch execute
        public ResultSetMapping BuildCommand(
            IUpdatableSql updatableSql, SqlSyntaxParser parser, Func<bool, string> generateParameterName)
        {
            var parameterInfo = ParamValue.ConvertParameters(
                _dataContext.AdoDbSqlProvider.SqlGenerationHelper,
                parser.SqlParameters,
                updatableSql.Parameters);

            this.AddParameters(parameterInfo);

            var resultSetMapping = this.BuildSql(parser.SqlStatement);

            this.AddAffectedCountOutputParameter(this.SqlBuilder, generateParameterName);

            return resultSetMapping;
        }

        //Batch execute
        public SqlSyntaxParser GetSqlParser(
            IUpdatableSql updatableSql, Func<bool, string> generateParameterName)
        {
            var parser = new SqlSyntaxParser(
                _dataContext.AdoDbSqlProvider.SqlGenerationHelper,
                _dataContext.AdoDbSqlProvider.TypeMapper);

            parser.Parse(updatableSql.SqlText, generateParameterName);

            return parser;
        }

        private ResultSetMapping BuildSql(string rawSql)
        {
            var updateSqlGenerator = _dataContext.AdoDbSqlProvider.UpdateSqlGenerator;

            var resultSetMapping = updateSqlGenerator.AppendRawUpdateSql(this.SqlBuilder, rawSql, 0);

            return resultSetMapping;
        }

        private void AddAffectedCountOutputParameter(StringBuilder sqlBuilder,
            Func<bool, string> generateParameterName)
        {
            string parameterName;

            if (generateParameterName == null)
            {
                parameterName = this.GetOutputParameterName();
            }
            else
            {
                parameterName = generateParameterName(true);
            }

            var sqlString = sqlBuilder.ToString();

            if (sqlString.Contains(_dataContext.AdoDbSqlProvider.SqlGenerationHelper.OutputParameterPlacehold))
            {
                sqlBuilder.Replace(
                    _dataContext.AdoDbSqlProvider.SqlGenerationHelper.OutputParameterPlacehold, 
                    parameterName);

                this.AddParameter(parameterName, null, typeof(int), ParameterDirection.Output);
            }
        }

        private string GetOutputParameterName()
        {
            var parameterName = "";

            var generateParameter = new ParameterNameGenerator("p");

            var parameters = this.GetAdoDbParameters();

            var isExist = true;

            while (isExist)
            {
                parameterName = generateParameter.GenerateNext(true);

                isExist = parameters.Any(m => m.ParameterName == parameterName);
            }

            return parameterName;
        }

        private SqlSyntaxParser BuildParameters(IUpdatableSql updatableSql)
        {
            var parser = new SqlSyntaxParser(
                _dataContext.AdoDbSqlProvider.SqlGenerationHelper,
                _dataContext.AdoDbSqlProvider.TypeMapper);

            parser.Parse(updatableSql.SqlText);

            var parameterInfo = ParamValue.ConvertParameters(
                _dataContext.AdoDbSqlProvider.SqlGenerationHelper, 
                parser.SqlParameters, 
                updatableSql.Parameters);

            this.AddParameters(parameterInfo);

            return parser;
        }
    }
}
